package com.hongji.lms5xxcommunicator.grid.export;

import com.hongji.lms5xxcommunicator.grid.core.RasterState;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * <p>
 * 导出服务（ExportService）：负责将当前栅格 {@link RasterState} 中的格中心数据导出为一组结构化点。<br/>
 * 坐标采用“格中心”的世界坐标（方案 A，与入库对齐策略一致）。
 * </p>
 *
 * <p><b>职责</b></p>
 * <ul>
 *   <li>导出全量或按步长抽样的栅格点。</li>
 *   <li>支持过滤：仅导出有数据的格（hasData）、按最小计数（minCount）。</li>
 *   <li>支持按 cell-key（r&lt;&lt;32 ^ c）集合导出指定格。</li>
 * </ul>
 *
 * <p><b>用法</b></p>
 * <ol>
 *   <li>构造：注入共享的 {@link RasterState}。</li>
 *   <li>全量/抽样导出：{@link #exportGridPoints(boolean, int, Integer)}。</li>
 *   <li>按键导出：{@link #fromKeys(Set, Integer)}。</li>
 * </ol>
 */
@Slf4j
public class ExportService {

    private final RasterState s;

    /**
     * <p>构造方法。</p>
     * @param s 共享的 {@link RasterState}（不可为 {@code null}）
     */
    public ExportService(RasterState s) {
        this.s = s;
    }

    /**
     * <p>导出网格点（全量或抽样）。</p>
     *
     * <p><b>过滤</b></p>
     * <ul>
     *   <li>{@code onlyHasData}：仅导出 {@code hasData == true} 的格。</li>
     *   <li>{@code stride}：抽样步长（同时作用于行与列；最小为 1）。</li>
     *   <li>{@code minCount}：最小采样计数门槛（可为 {@code null} 表示不启用）。</li>
     * </ul>
     *
     * @param onlyHasData  仅导出 hasData==true 的格
     * @param stride       抽样步长（行、列都应用；最小为 1）
     * @param minCount     最小采样数门槛（null 表示不启用；count < minCount 的格会被跳过）
     * @return 导出的网格点列表；异常或无数据时返回空集合，不为 {@code null}
     */
    public List<GridPoint> exportGridPoints(boolean onlyHasData, int stride, Integer minCount) {
        try {
            if (stride < 1) stride = 1;

            if (s == null || s.spec == null || s.hasData == null || s.meanZ == null || s.count == null) {
                log.error("RasterState 无效：spec/hasData/meanZ/count 为空");
                return Collections.emptyList();
            }

            final Snapshot snap = new Snapshot(
                    s.spec.rows, s.spec.cols,
                    s.spec.x0, s.spec.y0, s.spec.dx, s.spec.dy,
                    s.hasData, s.meanZ, s.count
            );

            if (snap.getRows() <= 0 || snap.getCols() <= 0) {
                log.warn("grid empty: rows={}, cols={}", snap.getRows(), snap.getCols());
                return Collections.emptyList();
            }

            final int est = Math.max(
                    16,
                    ((snap.getRows() + stride - 1) / stride) * ((snap.getCols() + stride - 1) / stride)
            );
            final List<GridPoint> out = new ArrayList<>(est);

            for (int r = 0; r < snap.getRows(); r += stride) {
                final double yc = snap.getY0() + (r + 0.5) * snap.getDy();
                final boolean[] hasRow = snap.getHas()[r];
                final double[]  meanRow = snap.getMean()[r];
                final int[]     cntRow  = snap.getCnt()[r];

                for (int c = 0; c < snap.getCols(); c += stride) {
                    final boolean has = hasRow[c];
                    final int cnt = cntRow[c];

                    if (onlyHasData && !has) continue;
                    if (minCount != null && minCount >= 1 && cnt < minCount) continue;

                    final double xc = snap.getX0() + (c + 0.5) * snap.getDx(); // 格中心 X
                    final int idx = r * snap.getCols() + c;

                    out.add(new GridPoint(
                            r, c, idx,
                            xc, yc,
                            meanRow[c],
                            cnt,
                            has
                    ));
                }
            }

            log.info("exportGridPoints ok: n={}, rows={}, cols={}, stride={}, onlyHasData={}, minCount={}",
                    out.size(), snap.getRows(), snap.getCols(), stride, onlyHasData, minCount);
            return out;
        } catch (Exception e) {
            log.error("exportGridPoints error", e);
            return Collections.emptyList();
        }
    }

    /**
     * <p>根据 cell key 集合（{@code r<<32 ^ c}）导出对应格点（坐标为格中心）。</p>
     *
     * @param keys     (r,c) 组合后的 key 集合
     * @param minCount 最小采样数门槛（可为 {@code null}，否则 {@code count < minCount} 将被过滤）
     * @return 指定键对应的格点列表；无匹配或异常时返回空列表
     */
    public List<GridPoint> fromKeys(Set<Long> keys, Integer minCount) {
        if (keys == null || keys.isEmpty()) return new ArrayList<>(0);
        if (s == null || s.spec == null || s.hasData == null || s.meanZ == null || s.count == null) {
            log.error("RasterState 无效：spec/hasData/meanZ/count 为空");
            return Collections.emptyList();
        }

        final int minCnt = (minCount != null && minCount >= 1) ? minCount : -1;
        final Snapshot snap = new Snapshot(
                s.spec.rows, s.spec.cols,
                s.spec.x0, s.spec.y0, s.spec.dx, s.spec.dy,
                s.hasData, s.meanZ, s.count
        );

        final List<GridPoint> out = new ArrayList<>(keys.size());
        for (long k : keys) {
            final int r = (int) (k >> 32);
            final int c = (int) (k & 0xffffffffL);
            if (r < 0 || r >= snap.getRows() || c < 0 || c >= snap.getCols()) continue;

            final boolean has = snap.getHas()[r][c];
            final int cnt = snap.getCnt()[r][c];
            if (minCnt >= 0 && cnt < minCnt) continue;

            final double xc = snap.getX0() + (c + 0.5) * snap.getDx();
            final double yc = snap.getY0() + (r + 0.5) * snap.getDy();
            final int idx = r * snap.getCols() + c;

            out.add(new GridPoint(
                    r, c, idx,
                    xc, yc,
                    snap.getMean()[r][c],
                    cnt,
                    has
            ));
        }
        return out;
    }

    /**
     * <p>快照当前 {@link RasterState}，减少循环内字段解引用。</p>
     * <p>值对象：使用 Lombok {@link Value} 保证不可变。</p>
     */
    @Value
    private static class Snapshot {
        int rows, cols;
        double x0, y0, dx, dy;
        boolean[][] has;
        double[][]  mean;
        int[][]     cnt;
    }
}
